{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "from fastai.basics import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "from nbdev.showdoc import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#default_exp callback.progress"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Progress and logging callbacks\n",
    "\n",
    "> Callback and helper function to track progress of training or log results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from fastai.test_utils import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ProgressCallback -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "@docs\n",
    "class ProgressCallback(Callback):\n",
    "    \"A `Callback` to handle the display of progress bars\"\n",
    "    run_after=Recorder\n",
    "\n",
    "    def before_fit(self):\n",
    "        assert hasattr(self.learn, 'recorder')\n",
    "        if self.create_mbar: self.mbar = master_bar(list(range(self.n_epoch)))\n",
    "        if self.learn.logger != noop:\n",
    "            self.old_logger,self.learn.logger = self.logger,self._write_stats\n",
    "            self._write_stats(self.recorder.metric_names)\n",
    "        else: self.old_logger = noop\n",
    "\n",
    "    def before_epoch(self):\n",
    "        if getattr(self, 'mbar', False): self.mbar.update(self.epoch)\n",
    "\n",
    "    def before_train(self):    self._launch_pbar()\n",
    "    def before_validate(self): self._launch_pbar()\n",
    "    def after_train(self):     self.pbar.on_iter_end()\n",
    "    def after_validate(self):  self.pbar.on_iter_end()\n",
    "    def after_batch(self):\n",
    "        self.pbar.update(self.iter+1)\n",
    "        if hasattr(self, 'smooth_loss'): self.pbar.comment = f'{self.smooth_loss:.4f}'\n",
    "\n",
    "    def _launch_pbar(self):\n",
    "        self.pbar = progress_bar(self.dl, parent=getattr(self, 'mbar', None), leave=False)\n",
    "        self.pbar.update(0)\n",
    "\n",
    "    def after_fit(self):\n",
    "        if getattr(self, 'mbar', False):\n",
    "            self.mbar.on_iter_end()\n",
    "            delattr(self, 'mbar')\n",
    "        if hasattr(self, 'old_logger'): self.learn.logger = self.old_logger\n",
    "\n",
    "    def _write_stats(self, log):\n",
    "        if getattr(self, 'mbar', False): self.mbar.write([f'{l:.6f}' if isinstance(l, float) else str(l) for l in log], table=True)\n",
    "\n",
    "    _docs = dict(before_fit=\"Setup the master bar over the epochs\",\n",
    "                 before_epoch=\"Update the master bar\",\n",
    "                 before_train=\"Launch a progress bar over the training dataloader\",\n",
    "                 before_validate=\"Launch a progress bar over the validation dataloader\",\n",
    "                 after_train=\"Close the progress bar over the training dataloader\",\n",
    "                 after_validate=\"Close the progress bar over the validation dataloader\",\n",
    "                 after_batch=\"Update the current progress bar\",\n",
    "                 after_fit=\"Close the master bar\")\n",
    "\n",
    "if not hasattr(defaults, 'callbacks'): defaults.callbacks = [TrainEvalCallback, Recorder, ProgressCallback]\n",
    "elif ProgressCallback not in defaults.callbacks: defaults.callbacks.append(ProgressCallback)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: left;\">\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>valid_loss</th>\n",
       "      <th>time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>6.149214</td>\n",
       "      <td>5.585020</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>5.495753</td>\n",
       "      <td>4.248405</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>4.743536</td>\n",
       "      <td>3.020540</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>4.007711</td>\n",
       "      <td>2.104298</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>3.349151</td>\n",
       "      <td>1.430777</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "learn = synth_learner()\n",
    "learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "@patch\n",
    "@contextmanager\n",
    "def no_bar(self:Learner):\n",
    "    \"Context manager that deactivates the use of progress bars\"\n",
    "    has_progress = hasattr(self, 'progress')\n",
    "    if has_progress: self.remove_cb(self.progress)\n",
    "    try: yield self\n",
    "    finally:\n",
    "        if has_progress: self.add_cb(ProgressCallback())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(#4) [0,30.605850219726562,27.92107391357422,'00:00']\n",
      "(#4) [1,26.819326400756836,19.888404846191406,'00:00']\n",
      "(#4) [2,22.556987762451172,13.134763717651367,'00:00']\n",
      "(#4) [3,18.57308578491211,8.311532020568848,'00:00']\n",
      "(#4) [4,15.115865707397461,5.124312400817871,'00:00']\n"
     ]
    }
   ],
   "source": [
    "learn = synth_learner()\n",
    "with learn.no_bar(): learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#hide\n",
    "#Check validate works without any training\n",
    "def tst_metric(out, targ): return F.mse_loss(out, targ)\n",
    "learn = synth_learner(n_trn=5, metrics=tst_metric)\n",
    "preds,targs = learn.validate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#hide\n",
    "#Check get_preds works without any training\n",
    "learn = synth_learner(n_trn=5, metrics=tst_metric)\n",
    "preds,targs = learn.validate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.before_fit\" class=\"doc_header\"><code>ProgressCallback.before_fit</code><a href=\"__main__.py#L7\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.before_fit</code>()\n",
       "\n",
       "Setup the master bar over the epochs"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_fit)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.before_epoch\" class=\"doc_header\"><code>ProgressCallback.before_epoch</code><a href=\"__main__.py#L15\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.before_epoch</code>()\n",
       "\n",
       "Update the master bar"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_epoch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.before_train\" class=\"doc_header\"><code>ProgressCallback.before_train</code><a href=\"__main__.py#L18\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.before_train</code>()\n",
       "\n",
       "Launch a progress bar over the training dataloader"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.before_validate\" class=\"doc_header\"><code>ProgressCallback.before_validate</code><a href=\"__main__.py#L19\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.before_validate</code>()\n",
       "\n",
       "Launch a progress bar over the validation dataloader"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_validate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.after_batch\" class=\"doc_header\"><code>ProgressCallback.after_batch</code><a href=\"__main__.py#L22\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.after_batch</code>()\n",
       "\n",
       "Update the current progress bar"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_batch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.after_train\" class=\"doc_header\"><code>ProgressCallback.after_train</code><a href=\"__main__.py#L20\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.after_train</code>()\n",
       "\n",
       "Close the progress bar over the training dataloader"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.after_validate\" class=\"doc_header\"><code>ProgressCallback.after_validate</code><a href=\"__main__.py#L21\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.after_validate</code>()\n",
       "\n",
       "Close the progress bar over the validation dataloader"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_validate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.after_fit\" class=\"doc_header\"><code>ProgressCallback.after_fit</code><a href=\"__main__.py#L30\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.after_fit</code>()\n",
       "\n",
       "Close the master bar"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_fit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ShowGraphCallback -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "class ShowGraphCallback(Callback):\n",
    "    \"Update a graph of training and validation loss\"\n",
    "    run_after,run_valid=ProgressCallback,False\n",
    "\n",
    "    def before_fit(self):\n",
    "        self.run = not hasattr(self.learn, 'lr_finder') and not hasattr(self, \"gather_preds\")\n",
    "        self.nb_batches = []\n",
    "        assert hasattr(self.learn, 'progress')\n",
    "\n",
    "    def after_train(self): self.nb_batches.append(self.train_iter)\n",
    "\n",
    "    def after_epoch(self):\n",
    "        \"Plot validation loss in the pbar graph\"\n",
    "        rec = self.learn.recorder\n",
    "        iters = range_of(rec.losses)\n",
    "        val_losses = [v[1] for v in rec.values]\n",
    "        x_bounds = (0, (self.n_epoch - len(self.nb_batches)) * self.nb_batches[0] + len(rec.losses))\n",
    "        y_bounds = (0, max((max(Tensor(rec.losses)), max(Tensor(val_losses)))))\n",
    "        self.progress.mbar.update_graph([(iters, rec.losses), (self.nb_batches, val_losses)], x_bounds, y_bounds)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: left;\">\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>valid_loss</th>\n",
       "      <th>time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>17.077604</td>\n",
       "      <td>15.280979</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>14.917645</td>\n",
       "      <td>10.782686</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>12.502088</td>\n",
       "      <td>6.969840</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>10.244695</td>\n",
       "      <td>4.306195</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>8.296035</td>\n",
       "      <td>2.599644</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>5</td>\n",
       "      <td>6.680259</td>\n",
       "      <td>1.545244</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>6</td>\n",
       "      <td>5.367113</td>\n",
       "      <td>0.914276</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>7</td>\n",
       "      <td>4.311401</td>\n",
       "      <td>0.539155</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>8</td>\n",
       "      <td>3.466442</td>\n",
       "      <td>0.317982</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>9</td>\n",
       "      <td>2.791286</td>\n",
       "      <td>0.188403</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#slow\n",
    "learn = synth_learner(cbs=ShowGraphCallback())\n",
    "learn.fit(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CSVLogger -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "class CSVLogger(Callback):\n",
    "    run_after=Recorder\n",
    "    \"Log the results displayed in `learn.path/fname`\"\n",
    "    def __init__(self, fname='history.csv', append=False):\n",
    "        self.fname,self.append = Path(fname),append\n",
    "\n",
    "    def read_log(self):\n",
    "        \"Convenience method to quickly access the log.\"\n",
    "        return pd.read_csv(self.path/self.fname)\n",
    "\n",
    "    def before_fit(self):\n",
    "        \"Prepare file with metric names.\"\n",
    "        self.path.parent.mkdir(parents=True, exist_ok=True)\n",
    "        self.file = (self.path/self.fname).open('a' if self.append else 'w')\n",
    "        self.file.write(','.join(self.recorder.metric_names) + '\\n')\n",
    "        self.old_logger,self.learn.logger = self.logger,self._write_line\n",
    "\n",
    "    def _write_line(self, log):\n",
    "        \"Write a line with `log` and call the old logger.\"\n",
    "        self.file.write(','.join([str(t) for t in log]) + '\\n')\n",
    "        self.old_logger(log)\n",
    "\n",
    "    def after_fit(self):\n",
    "        \"Close the file and clean up.\"\n",
    "        self.file.close()\n",
    "        self.learn.logger = self.old_logger"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results are appended to an existing file if `append`, or they overwrite it otherwise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: left;\">\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>valid_loss</th>\n",
       "      <th>time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>19.039587</td>\n",
       "      <td>19.701471</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>16.735422</td>\n",
       "      <td>14.259439</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>14.151361</td>\n",
       "      <td>9.632797</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>11.720305</td>\n",
       "      <td>6.176662</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>9.583822</td>\n",
       "      <td>3.861699</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "learn = synth_learner(cbs=CSVLogger())\n",
    "learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"CSVLogger.read_log\" class=\"doc_header\"><code>CSVLogger.read_log</code><a href=\"__main__.py#L8\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>CSVLogger.read_log</code>()\n",
       "\n",
       "Convenience method to quickly access the log."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(CSVLogger.read_log)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = learn.csv_logger.read_log()\n",
    "test_eq(df.columns.values, learn.recorder.metric_names)\n",
    "for i,v in enumerate(learn.recorder.values):\n",
    "    test_close(df.iloc[i][:3], [i] + v)\n",
    "os.remove(learn.path/learn.csv_logger.fname)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"CSVLogger.before_fit\" class=\"doc_header\"><code>CSVLogger.before_fit</code><a href=\"__main__.py#L12\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>CSVLogger.before_fit</code>()\n",
       "\n",
       "Prepare file with metric names."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(CSVLogger.before_fit)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"CSVLogger.after_fit\" class=\"doc_header\"><code>CSVLogger.after_fit</code><a href=\"__main__.py#L24\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>CSVLogger.after_fit</code>()\n",
       "\n",
       "Close the file and clean up."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(CSVLogger.after_fit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Export -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Converted 00_torch_core.ipynb.\n",
      "Converted 01_layers.ipynb.\n",
      "Converted 02_data.load.ipynb.\n",
      "Converted 03_data.core.ipynb.\n",
      "Converted 04_data.external.ipynb.\n",
      "Converted 05_data.transforms.ipynb.\n",
      "Converted 06_data.block.ipynb.\n",
      "Converted 07_vision.core.ipynb.\n",
      "Converted 08_vision.data.ipynb.\n",
      "Converted 09_vision.augment.ipynb.\n",
      "Converted 09b_vision.utils.ipynb.\n",
      "Converted 09c_vision.widgets.ipynb.\n",
      "Converted 10_tutorial.pets.ipynb.\n",
      "Converted 11_vision.models.xresnet.ipynb.\n",
      "Converted 12_optimizer.ipynb.\n",
      "Converted 13_callback.core.ipynb.\n",
      "Converted 13a_learner.ipynb.\n",
      "Converted 13b_metrics.ipynb.\n",
      "Converted 14_callback.schedule.ipynb.\n",
      "Converted 14a_callback.data.ipynb.\n",
      "Converted 15_callback.hook.ipynb.\n",
      "Converted 15a_vision.models.unet.ipynb.\n",
      "Converted 16_callback.progress.ipynb.\n",
      "Converted 17_callback.tracker.ipynb.\n",
      "Converted 18_callback.fp16.ipynb.\n",
      "Converted 18a_callback.training.ipynb.\n",
      "Converted 19_callback.mixup.ipynb.\n",
      "Converted 20_interpret.ipynb.\n",
      "Converted 20a_distributed.ipynb.\n",
      "Converted 21_vision.learner.ipynb.\n",
      "Converted 22_tutorial.imagenette.ipynb.\n",
      "Converted 23_tutorial.vision.ipynb.\n",
      "Converted 24_tutorial.siamese.ipynb.\n",
      "Converted 24_vision.gan.ipynb.\n",
      "Converted 30_text.core.ipynb.\n",
      "Converted 31_text.data.ipynb.\n",
      "Converted 32_text.models.awdlstm.ipynb.\n",
      "Converted 33_text.models.core.ipynb.\n",
      "Converted 34_callback.rnn.ipynb.\n",
      "Converted 35_tutorial.wikitext.ipynb.\n",
      "Converted 36_text.models.qrnn.ipynb.\n",
      "Converted 37_text.learner.ipynb.\n",
      "Converted 38_tutorial.text.ipynb.\n",
      "Converted 39_tutorial.transformers.ipynb.\n",
      "Converted 40_tabular.core.ipynb.\n",
      "Converted 41_tabular.data.ipynb.\n",
      "Converted 42_tabular.model.ipynb.\n",
      "Converted 43_tabular.learner.ipynb.\n",
      "Converted 44_tutorial.tabular.ipynb.\n",
      "Converted 45_collab.ipynb.\n",
      "Converted 46_tutorial.collab.ipynb.\n",
      "Converted 50_tutorial.datablock.ipynb.\n",
      "Converted 60_medical.imaging.ipynb.\n",
      "Converted 61_tutorial.medical_imaging.ipynb.\n",
      "Converted 65_medical.text.ipynb.\n",
      "Converted 70_callback.wandb.ipynb.\n",
      "Converted 71_callback.tensorboard.ipynb.\n",
      "Converted 72_callback.neptune.ipynb.\n",
      "Converted 73_callback.captum.ipynb.\n",
      "Converted 74_callback.cutmix.ipynb.\n",
      "Converted 97_test_utils.ipynb.\n",
      "Converted 99_pytorch_doc.ipynb.\n",
      "Converted index.ipynb.\n",
      "Converted tutorial.ipynb.\n"
     ]
    }
   ],
   "source": [
    "#hide\n",
    "from nbdev.export import notebook2script\n",
    "notebook2script()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "jupytext": {
   "split_at_heading": true
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
